/*
* @(#)StandardDrawingView.java 5.1
*
*/
package CH.ifa.draw.standard;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Panel;
import java.awt.Point;
import java.awt.PrintGraphics;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Enumeration;
import java.util.Vector;
import CH.ifa.draw.framework.Drawing;
import CH.ifa.draw.framework.DrawingChangeEvent;
import CH.ifa.draw.framework.DrawingEditor;
import CH.ifa.draw.framework.DrawingView;
import CH.ifa.draw.framework.Figure;
import CH.ifa.draw.framework.FigureEnumeration;
import CH.ifa.draw.framework.FigureSelection;
import CH.ifa.draw.framework.Handle;
import CH.ifa.draw.framework.Painter;
import CH.ifa.draw.framework.PointConstrainer;
import CH.ifa.draw.framework.Tool;
import CH.ifa.draw.util.Command;
import CH.ifa.draw.util.Geom;
/**
* The standard implementation of DrawingView.
* @see DrawingView
* @see Painter
* @see Tool
*/
public class StandardDrawingView
extends Panel
implements DrawingView,
MouseListener,
MouseMotionListener,
KeyListener {
/**
* The DrawingEditor of the view.
* @see #tool
* @see #setStatus
*/
transient private DrawingEditor fEditor;
/**
* The shown drawing.
*/
private Drawing fDrawing;
/**
* the accumulated damaged area
*/
private transient Rectangle fDamage = null;
/**
* The list of currently selected figures.
*/
transient private Vector fSelection;
/**
* The shown selection handles.
*/
transient private Vector fSelectionHandles;
/**
* The preferred size of the view
*/
private Dimension fViewSize;
/**
* The position of the last mouse click
* inside the view.
*/
private Point fLastClick;
/**
* A vector of optional backgrounds. The vector maintains
* a list a view painters that are drawn before the contents,
* that is in the background.
*/
private Vector fBackgrounds = null;
/**
* A vector of optional foregrounds. The vector maintains
* a list a view painters that are drawn after the contents,
* that is in the foreground.
*/
private Vector fForegrounds = null;
/**
* The update strategy used to repair the view.
*/
private Painter fUpdateStrategy;
/**
* The grid used to constrain points for snap to
* grid functionality.
*/
private PointConstrainer fConstrainer;
/*
* Serialization support. In JavaDraw only the Drawing is serialized.
* However, for beans support StandardDrawingView supports
* serialization
*/
private static final long serialVersionUID = -3878153366174603336L;
private int drawingViewSerializedDataVersion = 1;
/**
* Constructs the view.
*/
public StandardDrawingView(DrawingEditor editor, int width, int height) {
fEditor = editor;
fViewSize = new Dimension(width,height);
fLastClick = new Point(0, 0);
fConstrainer = null;
fSelection = new Vector();
setDisplayUpdate(new BufferedUpdateStrategy());
setBackground(Color.lightGray);
addMouseListener(this);
addMouseMotionListener(this);
addKeyListener(this);
}
/**
* Sets the view's editor.
*/
public void setEditor(DrawingEditor editor) {
fEditor = editor;
}
/**
* Gets the current tool.
*/
public Tool tool() {
return fEditor.tool();
}
/**
* Gets the drawing.
*/
public Drawing drawing() {
return fDrawing;
}
/**
* Sets and installs another drawing in the view.
*/
public void setDrawing(Drawing d) {
clearSelection();
if (fDrawing != null)
fDrawing.removeDrawingChangeListener(this);
fDrawing = d;
if (fDrawing != null)
fDrawing.addDrawingChangeListener(this);
checkMinimumSize();
repaint();
}
/**
* Gets the editor.
*/
public DrawingEditor editor() {
return fEditor;
}
/**
* Adds a figure to the drawing.
* @return the added figure.
*/
public Figure add(Figure figure) {
return drawing().add(figure);
}
/**
* Removes a figure from the drawing.
* @return the removed figure
*/
public Figure remove(Figure figure) {
return drawing().remove(figure);
}
/**
* Adds a vector of figures to the drawing.
*/
public void addAll(Vector figures) {
FigureEnumeration k = new FigureEnumerator(figures);
while (k.hasMoreElements())
add(k.nextFigure());
}
/**
* Gets the minimum dimension of the drawing.
*/
public Dimension getMinimumSize() {
return fViewSize;
}
/**
* Gets the preferred dimension of the drawing..
*/
public Dimension getPreferredSize() {
return getMinimumSize();
}
/**
* Sets the current display update strategy.
* @see UpdateStrategy
*/
public void setDisplayUpdate(Painter updateStrategy) {
fUpdateStrategy = updateStrategy;
}
/**
* Gets the currently selected figures.
* @return a vector with the selected figures. The vector
* is a copy of the current selection.
*/
public Vector selection() {
// protect the vector with the current selection
return (Vector)fSelection.clone();
}
/**
* Gets an enumeration over the currently selected figures.
*/
public FigureEnumeration selectionElements() {
return new FigureEnumerator(fSelection);
}
/**
* Gets the currently selected figures in Z order.
* @see #selection
* @return a vector with the selected figures. The vector
* is a copy of the current selection.
*/
public Vector selectionZOrdered() {
Vector result = new Vector(fSelection.size());
FigureEnumeration figures = drawing().figures();
while (figures.hasMoreElements()) {
Figure f= figures.nextFigure();
if (fSelection.contains(f)) {
result.addElement(f);
}
}
return result;
}
/**
* Gets the number of selected figures.
*/
public int selectionCount() {
return fSelection.size();
}
/**
* Adds a figure to the current selection.
*/
public void addToSelection(Figure figure) {
if (!fSelection.contains(figure)) {
fSelection.addElement(figure);
fSelectionHandles = null;
figure.invalidate();
selectionChanged();
}
}
/**
* Adds a vector of figures to the current selection.
*/
public void addToSelectionAll(Vector figures) {
FigureEnumeration k = new FigureEnumerator(figures);
while (k.hasMoreElements())
addToSelection(k.nextFigure());
}
/**
* Removes a figure from the selection.
*/
public void removeFromSelection(Figure figure) {
if (fSelection.contains(figure)) {
fSelection.removeElement(figure);
fSelectionHandles = null;
figure.invalidate();
selectionChanged();
}
}
/**
* If a figure isn't selected it is added to the selection.
* Otherwise it is removed from the selection.
*/
public void toggleSelection(Figure figure) {
if (fSelection.contains(figure))
removeFromSelection(figure);
else
addToSelection(figure);
selectionChanged();
}
/**
* Clears the current selection.
*/
public void clearSelection() {
Figure figure;
FigureEnumeration k = selectionElements();
while (k.hasMoreElements())
k.nextFigure().invalidate();
fSelection = new Vector();
fSelectionHandles = null;
selectionChanged();
}
/**
* Gets an enumeration of the currently active handles.
*/
private Enumeration selectionHandles() {
if (fSelectionHandles == null) {
fSelectionHandles = new Vector();
FigureEnumeration k = selectionElements();
while (k.hasMoreElements()) {
Figure figure = k.nextFigure();
Enumeration kk = figure.handles().elements();
while (kk.hasMoreElements())
fSelectionHandles.addElement(kk.nextElement());
}
}
return fSelectionHandles.elements();
}
/**
* Gets the current selection as a FigureSelection. A FigureSelection
* can be cut, copied, pasted.
*/
public FigureSelection getFigureSelection() {
return new FigureSelection(selectionZOrdered());
}
/**
* Finds a handle at the given coordinates.
* @return the hit handle, null if no handle is found.
*/
public Handle findHandle(int x, int y) {
Handle handle;
Enumeration k = selectionHandles();
while (k.hasMoreElements()) {
handle = (Handle) k.nextElement();
if (handle.containsPoint(x, y))
return handle;
}
return null;
}
/**
* Informs that the current selection changed.
* By default this event is forwarded to the
* drawing editor.
*/
protected void selectionChanged() {
fEditor.selectionChanged(this);
}
/**
* Gets the position of the last click inside the view.
*/
public Point lastClick() {
return fLastClick;
}
/**
* Sets the grid spacing that is used to constrain points.
*/
public void setConstrainer(PointConstrainer c) {
fConstrainer = c;
}
/**
* Gets the current constrainer.
*/
public PointConstrainer getConstrainer() {
return fConstrainer;
}
/**
* Constrains a point to the current grid.
*/
protected Point constrainPoint(Point p) {
// constrin to view size
Dimension size = getSize();
//p.x = Math.min(size.width, Math.max(1, p.x));
//p.y = Math.min(size.height, Math.max(1, p.y));
p.x = Geom.range(1, size.width, p.x);
p.y = Geom.range(1, size.height, p.y);
if (fConstrainer != null )
return fConstrainer.constrainPoint(p);
return p;
}
/**
* Handles mouse down events. The event is delegated to the
* currently active tool.
* @return whether the event was handled.
*/
public void mousePressed(MouseEvent e) {
requestFocus(); // JDK1.1
Point p = constrainPoint(new Point(e.getX(), e.getY()));
fLastClick = new Point(e.getX(), e.getY());
tool().mouseDown(e, p.x, p.y);
checkDamage();
}
/**
* Handles mouse drag events. The event is delegated to the
* currently active tool.
* @return whether the event was handled.
*/
public void mouseDragged(MouseEvent e) {
Point p = constrainPoint(new Point(e.getX(), e.getY()));
tool().mouseDrag(e, p.x, p.y);
checkDamage();
}
/**
* Handles mouse move events. The event is delegated to the
* currently active tool.
* @return whether the event was handled.
*/
public void mouseMoved(MouseEvent e) {
tool().mouseMove(e, e.getX(), e.getY());
}
/**
* Handles mouse up events. The event is delegated to the
* currently active tool.
* @return whether the event was handled.
*/
public void mouseReleased(MouseEvent e) {
Point p = constrainPoint(new Point(e.getX(), e.getY()));
tool().mouseUp(e, p.x, p.y);
checkDamage();
}
/**
* Handles key down events. Cursor keys are handled
* by the view the other key events are delegated to the
* currently active tool.
* @return whether the event was handled.
*/
public void keyPressed(KeyEvent e) {
int code = e.getKeyCode();
if ((code == KeyEvent.VK_BACK_SPACE) || (code == KeyEvent.VK_DELETE)) {
Command cmd = new DeleteCommand("Delete", this);
cmd.execute();
} else if (code == KeyEvent.VK_DOWN || code == KeyEvent.VK_UP ||
code == KeyEvent.VK_RIGHT || code == KeyEvent.VK_LEFT) {
handleCursorKey(code);
} else {
tool().keyDown(e, code);
}
checkDamage();
}
/**
* Handles cursor keys by moving all the selected figures
* one grid point in the cursor direction.
*/
protected void handleCursorKey(int key) {
int dx = 0, dy = 0;
int stepX = 1, stepY = 1;
// should consider Null Object.
if (fConstrainer != null) {
stepX = fConstrainer.getStepX();
stepY = fConstrainer.getStepY();
}
switch (key) {
case KeyEvent.VK_DOWN:
dy = stepY;
break;
case KeyEvent.VK_UP:
dy = -stepY;
break;
case KeyEvent.VK_RIGHT:
dx = stepX;
break;
case KeyEvent.VK_LEFT:
dx = -stepX;
break;
}
moveSelection(dx, dy);
}
private void moveSelection(int dx, int dy) {
FigureEnumeration figures = selectionElements();
while (figures.hasMoreElements())
figures.nextFigure().moveBy(dx, dy);
checkDamage();
}
/**
* Refreshes the drawing if there is some accumulated damage
*/
public synchronized void checkDamage() {
Enumeration each = drawing().drawingChangeListeners();
while (each.hasMoreElements()) {
Object l = each.nextElement();
if (l instanceof DrawingView) {
((DrawingView)l).repairDamage();
}
}
}
public void repairDamage() {
if (fDamage != null) {
repaint(fDamage.x, fDamage.y, fDamage.width, fDamage.height);
fDamage = null;
}
}
public void drawingInvalidated(DrawingChangeEvent e) {
Rectangle r = e.getInvalidatedRectangle();
if (fDamage == null)
fDamage = r;
else
fDamage.add(r);
}
public void drawingRequestUpdate(DrawingChangeEvent e) {
repairDamage();
}
/**
* Updates the drawing view.
*/
public void update(Graphics g) {
paint(g);
}
/**
* Paints the drawing view. The actual drawing is delegated to
* the current update strategy.
* @see Painter
*/
public void paint(Graphics g) {
fUpdateStrategy.draw(g, this);
}
/**
* Draws the contents of the drawing view.
* The view has three layers: background, drawing, handles.
* The layers are drawn in back to front order.
*/
public void drawAll(Graphics g) {
boolean isPrinting = g instanceof PrintGraphics;
drawBackground(g);
if (fBackgrounds != null && !isPrinting)
drawPainters(g, fBackgrounds);
drawDrawing(g);
if (fForegrounds != null && !isPrinting)
drawPainters(g, fForegrounds);
if (!isPrinting)
drawHandles(g);
}
/**
* Draws the currently active handles.
*/
public void drawHandles(Graphics g) {
Enumeration k = selectionHandles();
while (k.hasMoreElements())
((Handle) k.nextElement()).draw(g);
}
/**
* Draws the drawing.
*/
public void drawDrawing(Graphics g) {
fDrawing.draw(g);
}
/**
* Draws the background. If a background pattern is set it
* is used to fill the background. Otherwise the background
* is filled in the background color.
*/
public void drawBackground(Graphics g) {
g.setColor(getBackground());
g.fillRect(0, 0, getBounds().width, getBounds().height);
}
private void drawPainters(Graphics g, Vector v) {
for (int i = 0; i < v.size(); i++)
((Painter)v.elementAt(i)).draw(g, this);
}
/**
* Adds a background.
*/
public void addBackground(Painter painter) {
if (fBackgrounds == null)
fBackgrounds = new Vector(3);
fBackgrounds.addElement(painter);
repaint();
}
/**
* Removes a background.
*/
public void removeBackground(Painter painter) {
if (fBackgrounds != null)
fBackgrounds.removeElement(painter);
repaint();
}
/**
* Removes a foreground.
*/
public void removeForeground(Painter painter) {
if (fForegrounds != null)
fForegrounds.removeElement(painter);
repaint();
}
/**
* Adds a foreground.
*/
public void addForeground(Painter painter) {
if (fForegrounds == null)
fForegrounds = new Vector(3);
fForegrounds.addElement(painter);
repaint();
}
/**
* Freezes the view by acquiring the drawing lock.
* @see Drawing#lock
*/
public void freezeView() {
drawing().lock();
}
/**
* Unfreezes the view by releasing the drawing lock.
* @see Drawing#unlock
*/
public void unfreezeView() {
drawing().unlock();
}
private void readObject(ObjectInputStream s)
throws ClassNotFoundException, IOException {
s.defaultReadObject();
fSelection = new Vector(); // could use lazy initialization instead
if (fDrawing != null)
fDrawing.addDrawingChangeListener(this);
}
private void checkMinimumSize() {
FigureEnumeration k = drawing().figures();
Dimension d = new Dimension(0, 0);
while (k.hasMoreElements()) {
Rectangle r = k.nextFigure().displayBox();
d.width = Math.max(d.width, r.x+r.width);
d.height = Math.max(d.height, r.y+r.height);
}
if (fViewSize.height < d.height || fViewSize.width < d.width) {
fViewSize.height = d.height+10;
fViewSize.width = d.width+10;
setSize(fViewSize);
}
}
public boolean isFocusTraversable() {
return true;
}
// listener methods we are not interested in
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mouseClicked(MouseEvent e) {}
public void keyTyped(KeyEvent e) {}
public void keyReleased(KeyEvent e) {}
}